﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using fess.Graphics;
using fess.Maps;
using Microsoft.Xna.Framework;
using fess.Screens;

namespace fess.RPG
{
  static class RPGResources
  {
    public static Texture2D SkeletonFace;
    public static SpriteSheet SkeletonWalk, BloodySkeletonWalk;
  }

  interface ITalkingCharacter
  {
    void Talk();
  }

  class ActionCharacter : IActive
  {
    Texture2D facePic;
    int strength, spirituality;
    double mana, health, speed;
    float radius;
    string name;
    int level;
    WalkingCharacter character;
    GenericMap map = null;

    public event Action Death, Damage;

    TimeSpan lastAttack = TimeSpan.Zero;

    List<IAI> behaviors = new List<IAI>();

    internal virtual GenericMap Map
    {
      get { return map; }
      set { map = value; }
    }

    public WalkingCharacter Character
    {
      get { return character; }
      set { character = value; }
    }

    public float Radius
    {
      get { return radius; }
      set { radius = value; }
    }

    public double Speed
    {
      get { return speed; }
      set { speed = value; }
    }

    public string Name
    {
      get { return name; }
      set { name = value; }
    }

    public int Level
    {
      get { return level; }
      set 
      {
        var manaPercent = Mana / MaxMana;
        var healthPercent = Health / MaxHealth;
        level = value;
        Mana = MaxMana * manaPercent;
        Health = MaxHealth * healthPercent;
      }
    }

    public double MaxHealth
    {
      get { return strength * level * 5; }
    }

    public double Health
    {
      get { return health; }
      set
      {
        if (value == 0 && !Dead)
        {
          character.Die();
          for (int i = 0; i < 10; ++i)
            Bleed(new Vector2((float)Misc.Rand.NextDouble() - 0.5f, (float)Misc.Rand.NextDouble() - 0.5f) * 20,
              Vector2.Zero, TimeSpan.FromSeconds(i * 0.5));

          health = 0;
          if (Death != null)
            Death();
        }

        health = value;
      }
    }

    protected virtual void Bleed(Vector2 relativePos, Vector2 dir, TimeSpan delay)
    {
      map.drawables.Add(new BloodSplatter(FXResources.blood, character.Position + relativePos, dir,
        map.game.time + delay, map.passMap, Color.White, true, 1 / 1.3f));
    }

    public double MaxMana
    {
      get { return spirituality * level * 5; }
    }

    public double Mana
    {
      get { return mana; }
      set { mana = Math.Min(MaxMana, value); }
    }

    public ActionCharacter(WalkingCharacter character, Texture2D facePic, string name, int strength, int spirituality, int level, double speed,
      float radius = 13)
    {
      this.facePic = facePic;
      this.name = name;
      this.level = level;
      this.strength = strength;
      this.spirituality = spirituality;
      this.speed = speed;
      this.radius = radius;
      this.character = character;
      mana = MaxMana;
      health = MaxHealth;
    }

    public Texture2D FacePic { get { return facePic; } }

    public void MeleeAttack(ActionCharacter target)
    {
      if ((map.game.time - lastAttack).TotalSeconds > 1 / speed / 10)
      {
        character.Attack(target.Character.Position, map.game.time);
        target.DealDamage(Misc.Dice(level, strength), this, map.game.time);
        lastAttack = map.game.time;
      }
    }

    public void Fireball(Vector2 target)
    {
      if ((map.game.time - lastAttack).TotalSeconds > 1 / speed / 10 && mana > FessGame.FireballCost)
      {
        mana -= FessGame.FireballCost;
        var dir = target - character.Position;
        character.Direction = dir;
        var power = 10 + Level + (this == map.game.fess && map.game.fireBonus ? 3 : 0);
        map.activities.Add(new Projectile(character.Position + dir * 30 / dir.Length(), dir, power, true, this, map));
        lastAttack = map.game.time;
      }
    }

    public void FireArrow(Vector2 target)
    {
      if ((map.game.time - lastAttack).TotalSeconds > 1 / speed / 10 && mana > FessGame.FireArrowCost)
      {
        mana -= FessGame.FireArrowCost;
        var dir = target - character.Position;
        character.Direction = dir;
        var power = 5 + Level + (this == map.game.fess && map.game.fireBonus ? 3 : 0);
        map.activities.Add(new Projectile(character.Position + dir * 30 / dir.Length(), dir, power, false, this, map));
        lastAttack = map.game.time;
      }
    }

    public void DealDamage(int damage, ActionCharacter attacker, TimeSpan when)
    {
      if (damage == 0)
        return;

      var myPos = character.Position;
      var attackerPos = attacker.Character.Position;
      var dir = character.Position - attackerPos;
      var hitShift = dir.LimitLength(5);
      Shift(hitShift);

      if (Dead)
        return;

      map.drawables.Add(new FX(Misc.Rand.Next(2) == 0 ? FXResources.smallHit2 : FXResources.smallHit1, myPos,
        (float)Math.Atan2(dir.Y, dir.X), 1, when));
      Bleed(Vector2.Zero, hitShift * (2 + (float)Misc.Rand.NextDouble() * 3) + Misc.ModArg((float)Misc.Rand.NextDouble() * 15, Misc.RandAngle()),
        TimeSpan.Zero);
      var myDir = character.Direction;
      character.Direction = myDir;

      Health = Math.Max(0, Health - damage);

      if (Damage != null)
        Damage();
    }

    public bool Shift(Vector2 shift)
    {
      var newPos = map.MoveObject(character.Position, character.Position + shift, radius, this);
      bool success = newPos != character.Position;
      if (success)
        character.Position = newPos;
      return success;
    }

    public bool Dead { get { return health == 0; } }

    public bool Update(TimeSpan time)
    {
      if (Dead)
        return true;

      mana = Math.Min(MaxMana, mana + map.game.elapsedTime.TotalSeconds * MaxMana / 20);

      if (behaviors.Count > 0)
      {
        var ai = behaviors.OrderByDescending(b => b.Priority).First();
        if (!ai.Update(time))
          behaviors.Remove(ai);
      }

      return true;
    }

    public void Say(string what, double waitSeconds = 0, double showSeconds = 0)
    {
      Map.messages.Add(new Screens.MessageWindow(this, what, Map.game.time + TimeSpan.FromSeconds(waitSeconds),
        showSeconds == 0 ? what.Count(c => c == '\n') * 3 + 5 : showSeconds));
    }

    public void GirlSay(string what, Texture2D pic, double waitSeconds)
    {
      Map.messages.Add(new Screens.MessageWindow(this, what, Map.game.time + TimeSpan.FromSeconds(waitSeconds), pic, what.Count(c => c == '\n') * 3 + 5));
    }

    public void SayWithAnswers(string what, double waitSeconds, params Answer[] answers)
    {
      Map.messages.Add(new Screens.AnswerWindow(this, what, Map.game.time + TimeSpan.FromSeconds(waitSeconds), answers));
    }

    public void AddAI(IAI ai)
    {
      behaviors.Add(ai);
    }

    public void DropAIs()
    {
      behaviors.Clear();
    }

    public virtual ActionCharacter MakeUndead()
    {
      if (Dead)
      {
        for (int i = 0; i < 15; ++i)
          Bleed(new Vector2((float)Misc.Rand.NextDouble() - 0.5f, (float)Misc.Rand.NextDouble() - 0.5f) * 35,
            Vector2.Zero, TimeSpan.FromSeconds(i * 0.2));

        character.Remove();
        var undead = new Skeleton(Level + (map.game.undeadBonus ? 1 : 0), true);
        map.RemoveCharacter(this);
        undead.Character.Position = character.Position;
        undead.hostile = false;
        return undead;
      }

      return null;
    }
  }

  class Skeleton : ActionCharacter, ITalkingCharacter
  {
    public bool hostile = true, calmed = false, bloody;
    double calmHealth = 0;

    public Skeleton(int level, bool bloody = false)
      : base(new WalkingCharacter(bloody ? RPGResources.BloodySkeletonWalk : RPGResources.SkeletonWalk), RPGResources.SkeletonFace, "Скелет",
        3, 0, level, 0.07)
    {
      this.bloody = bloody;
    }

    protected override void Bleed(Vector2 relativePos, Vector2 dir, TimeSpan delay)
    {
    }

    public void Talk()
    {
      if (hostile)
      {
        if (Map.game.fess.Mana >= FessGame.TurnUndeadCost)
        {
          hostile = false;
          Map.game.fess.Mana -= FessGame.TurnUndeadCost;
          DropAIs();
          Map.ChangeFaction(this, false);
          if (Map.game.undeadBonus)
            ++Level;
          AddAI(new MeleeAI(this, false));
        }
      }
      else
      {
        calmed = true;
        calmHealth = Health;
        Health = 0;
      }
    }

    public override ActionCharacter MakeUndead()
    {
      if (calmed && calmHealth > 0)
      {
        Health = calmHealth;
        calmed = false;
        Character.Rise();
        Map.ChangeFaction(this, false);
        return this;
      }

      return null;
    }
  }

  class Daenur : ActionCharacter, ITalkingCharacter
  {
    bool talkInterrupted = false;

    public Daenur(FessGame game)
      : base(new WalkingCharacter(new CharacterMovementSheet(game.Content.Load<Texture2D>("chars/daenur"))),
        game.Content.Load<Texture2D>("chars/daenur-face"), "Даэнур", 4, 5, 80, 0.09)
    {
    }

    public void Talk()
    {
      var fess = Map.game.fess;

      switch (Map.game.CemeteryQuest)
      {
        case FessGame.CemeteryQuestStatus.Unknown:
          if (!talkInterrupted)
          {
            Map.game.AddActivity(new Dialog(this,
                () => { Map.game.CemeteryQuest = FessGame.CemeteryQuestStatus.Began; talkInterrupted = false; },
                () => { Map.InvokeDropMessages(); Say("Неясыть, ты куда? Мы не закончили!"); talkInterrupted = true; },
              new Dialog.Line(this, "Здравствуй, Неясыть.\nГотов к своей первой самостоятельной работе?"),
              new Dialog.Line(fess, "Да, учитель. Я выполню эти задания,\nесли уж это так необходимо."),
              new Dialog.Line(this, "Ты сомневаешься в моём методе обучения?"),
              new Dialog.Line(fess, "Нет, но...\nВы сами говорили, что у нас мало времени.\nНе принесёт ли чтение книг больше пользы?"),
              new Dialog.Line(this, "Теоретические знания - мёртвый груз без опыта\nприменения. Какой ты Некромант, если ни разу\nне встречался с Нежитью?"),
              new Dialog.Line(fess, "Ладно, ладно... Я готов."),
              new Dialog.Line(this, "Мне пришла жалоба на неупокоенных от\nсмотрителя кладбища за городом. Пойди туда и\nобеспечь упокоение Нежити.")));
          }
          else
          {
            Map.game.AddActivity(new Dialog(this,
                () => { Map.game.CemeteryQuest = FessGame.CemeteryQuestStatus.Began; talkInterrupted = false; },
                () => { Map.InvokeDropMessages(); Say("Неясыть, ты куда? Мы не закончили!"); talkInterrupted = true; },
              new Dialog.Line(this, "Так ты готов к первому заданию?"),
              new Dialog.Line(fess, "Да, наставник."),
              new Dialog.Line(this, "Мне пришла жалоба на неупокоенных от\nсмотрителя кладбища за городом. Пойди туда и\nобеспечь упокоение Нежити.")));
          }
          break;
        case FessGame.CemeteryQuestStatus.Began:
          fess.Say("Не стоит зря беспокоить Даэнура.\nВернусь, когда закончу работу.");
          break;
        case FessGame.CemeteryQuestStatus.Undeads:
          if(Map.game.turnUndead)
            fess.Say("Не стоит зря беспокоить Даэнура.\nВернусь, когда закончу работу.");
          else
            Map.game.AddActivity(new Dialog(this,
                () => { Map.game.turnUndead = true; },
                () => { Map.InvokeDropMessages(); Say("Неясыть, ты куда? Мы не закончили!"); },
              new Dialog.Line(this, "Как продвигается задание, Неясыть?"),
              new Dialog.Line(fess, "Неважно... От моих попыток упокоить Нежить\nнарушается упокоенность других."),
              new Dialog.Line(this, "Это потому, что ты пытаешься их уничтожить,\nа не упокоить. Используй силу Тьмы,\nчтобы подчинить себе Нежить."),
              new Dialog.Line(fess, "Хорошо, я попробую. Нервы сдали, когда\nувидел этих скелетов впервые, вот\nвсё Учение и вылетело из головы.")));
          break;
        case FessGame.CemeteryQuestStatus.Done:
          if(Map.game.cemetery.DestroyedUndeads > 5)
            Map.game.AddActivity(new Dialog(this,
                () => { Map.game.CemeteryQuest = FessGame.CemeteryQuestStatus.Reported; Map.game.rank += 1; },
                () => { Map.InvokeDropMessages(); Say("Неясыть, ты куда? Мы не закончили!"); },
              new Dialog.Line(this, "Я видел всё, что произошло.\nДля первого раза..."),
              new Dialog.Line(this, "УЖАСНО!"),
              new Dialog.Line(fess, "Но что я мог поделать? Из-за моей волшбы\nнеупокоенность только усиливается!"),
              new Dialog.Line(this, "Ты должен был воспользоваться своей\nвластью над Нежитью. Не уничтожать Тьму,\nа подчинить и использовать её."),
              new Dialog.Line(this, "Придётся повторить кое-что из пройденного\nматериала. Потом поможешь смотрителю\nпривести кладбище в порядок."),
              new Dialog.Line(this, "Возвращайся, когда будешь готов продолжать\nпрактику.")
              ));
          else if(Map.game.cemetery.DestroyedUndeads > 1)
            Map.game.AddActivity(new Dialog(this,
                () => { Map.game.CemeteryQuest = FessGame.CemeteryQuestStatus.Reported; Map.game.rank += 2; },
                () => { Map.InvokeDropMessages(); Say("Неясыть, ты куда? Мы не закончили!"); },
              new Dialog.Line(this, "Я видел всё, что произошло.\nДля первого раза..."),
              new Dialog.Line(this, "Не так уж и плохо. Хотя ты мог бы и более\nграмотно использовать учение Тьмы."),
              new Dialog.Line(fess, "Да, тут необходима тренировка."),
              new Dialog.Line(this, "Я выдам тебе следующее задание, когда ты\nбудешь готов.")
              ));
          else
            Map.game.AddActivity(new Dialog(this,
                () => { Map.game.CemeteryQuest = FessGame.CemeteryQuestStatus.Reported; Map.game.rank += 3; },
                () => { Map.InvokeDropMessages(); Say("Неясыть, ты куда? Мы не закончили!");  },
              new Dialog.Line(this, "Я видел всё, что произошло.\nДля первого раза..."),
              new Dialog.Line(this, "Хорошо. Ты эффективно использовал учение\nТьмы."),
              new Dialog.Line(fess, "Спасибо, учитель."),
              new Dialog.Line(this, "Я выдам тебе следующее задание, когда ты\nбудешь готов.")
              ));
          break;

        case FessGame.CemeteryQuestStatus.Reported:
          if (fess.Level == 1)
          {
            Map.game.AddActivity(new Dialog(this,
                () => { },
                () => { Map.InvokeDropMessages(); Say("Неясыть, ты куда? Мы не закончили!"); },
              new Dialog.Line(fess, "Даэнур, на последнем задании я получил\nмного опыта, и я хочу кое-что обсудить."),
              new Dialog.Line(this, "Хорошо. Выбери тему занятия.",
                  new Answer("Огонь", () =>
                  {
                    Map.game.AddActivity(new Dialog(this,
                        () =>
                        {
                          Map.Fade(() =>
                            {
                              fess.Say("Значит, если заставить огненных духов покинуть\nсвои обиталища, то я смогу контролировать\nомертвлённый объём, подобно Нежити.");
                              fess.Level += 1;
                              Map.game.fireBonus = true;
                            });
                        },
                        () => { Map.InvokeDropMessages(); Say("Неясыть, ты куда? Мы не закончили!"); },
                        new Dialog.Line(this, "Точка зрения, что магия Стихии и магия Тьмы\nнесовместимы - ошибочна. Используя Тьму,\nты можешь контролировать Стихию.")
                    ));
                  }),

                  new Answer("Смерть", () =>
                  {
                    Map.game.AddActivity(new Dialog(this,
                        () =>
                        {
                          Map.Fade(() =>
                            {
                              fess.Say("Понимаю. Теперь я смогу лучше повелевать\nНежитью.");
                              fess.Level += 1;
                              Map.game.undeadBonus = true;
                            });
                        },
                        () => { Map.InvokeDropMessages(); Say("Неясыть, ты куда? Мы не закончили!"); },
                        new Dialog.Line(this, "Некромантия есть умение обращать свою силу\nна связь с отзвуками, сиречь с тенями усопших.")
                    ));
                  })
                )
              ));
          }
          else
          {
            switch (Map.game.CaveQuest)
            {
              case FessGame.CaveQuestStatus.Unknown:
                Map.game.AddActivity(new Dialog(this,
                    () => { Map.game.CaveQuest = FessGame.CaveQuestStatus.Began; },
                    () => { Map.InvokeDropMessages(); Say("Неясыть, ты куда? Мы не закончили!"); },
                  new Dialog.Line(this, "Ты готов выполнить следующее задание?"),
                  new Dialog.Line(fess, "Да, Тёмный. Уверен, что в этот раз\nя справлюсь лучше."),
                  new Dialog.Line(this, "Недавно я почувствовал активность Тьмы\nв пещере неподалёку от города. Возможно,\nтам применялись ритуальные пытки."),
                  new Dialog.Line(this, "Это противозаконно и может плохо\nсказаться на нас, если Светлые это обнаружат.\nВыясни обстановку и возвращайся."),
                  new Dialog.Line(this, "Потом мы вместе решим, как поступить.")
                  ));
                break;
              case FessGame.CaveQuestStatus.Began:
                fess.Say("Не стоит зря беспокоить Даэнура.\nВернусь, когда закончу работу.");
                break;
              case FessGame.CaveQuestStatus.Done:
                Map.game.AddActivity(new Dialog(this,
                    () => { Map.Fade(() => Map.game.SwitchScreen(new Victory(Map.game))); },
                    () => { Map.InvokeDropMessages(); Say("Неясыть, ты куда? Мы не закончили!"); },
                  new Dialog.Line(this, "Что ты обнаружил, Неясыть?"),
                  new Dialog.Line(fess, "Группа магов напала на меня до того,\nкак я смог проникнуть в пещеру. Из самой\nпещеры исходит сильнейшая магическая аура."),
                  new Dialog.Line(fess, "Я решил внутри не ходить - там,\nвероятно, смертельно опасно. Думаю, Вы\nправы - это подпольные Тёмные."),
                  new Dialog.Line(this, "Этого я и боялся. Завтра мы отправимся\nтуда вместе и попробуем с ними договориться."),
                  new Dialog.Line(this, "Возможно, придётся от них избавиться."),
                  new Dialog.Line(this, "Свою практику считай пройденной.\nТы отлично справился в трудных\nобстоятельствах.")
                  ));
                break;
            }
          }
          break;
      }
    }
  }

  class TavernGuy : ActionCharacter, ITalkingCharacter
  {
    public TavernGuy(WalkingCharacter character, Texture2D facePic, string name)
      : base(character, facePic, name, 2, 4, 15 + Misc.Rand.Next(10), 0.09)
    {
    }

    public virtual void Talk()
    {
      var goAway = new[] { "Отвали, Неясыть, не до тебя.", "Я занят.", "Чего уставился, тёмный?" };
      Say(goAway[Misc.Rand.Next(goAway.Length)]);
    }
  }

  class TavernOwner : TavernGuy
  {
    TimeSpan drank = -TimeSpan.FromHours(1);

    public TavernOwner(WalkingCharacter character, Texture2D facePic, string name)
      : base(character, facePic, name)
    {
    }

    public override void Talk()
    {
      var fess = Map.game.fess;

      var bye = new[] { "Пока!", "Заходи ещё.", "Удачного дня." };

      if (Map.game.time - drank < TimeSpan.FromMinutes(3))
        Say("Тебе уже хватит, Неясыть.");
      else
      {
        Map.game.AddActivity(new Dialog(this,
            () => { },
            () => { Map.InvokeDropMessages(); Say(bye[Misc.Rand.Next(bye.Length)]); },
          new Dialog.Line(this, "Чего-нибудь выпить?", 
            new Answer("Выпить", () =>
            {
              fess.Say("Почему бы и нет? Накачу кружечку.");
              drank = Map.game.time;
              fess.Mana += fess.MaxMana * 0.3;
            }),
            new Answer("Отказаться", () => fess.Say("Не сейчас.")))
          ));
      }
    }
  }

  class Atlika : ActionCharacter, ITalkingCharacter
  {
    Texture2D disgusted, playful, pleased, scared, shy;

    public Atlika(FessGame game) :
      base(new WalkingCharacter(new CharacterMovementSheet(game.Content.Load<Texture2D>("chars/atlika/walk"))),
      game.Content.Load<Texture2D>("chars/atlika/normal"), "Атлика", 1, 0, 5, 0.09)
    {
      disgusted = game.Content.Load<Texture2D>("chars/atlika/disgusted");
      playful = game.Content.Load<Texture2D>("chars/atlika/playful");
      pleased = game.Content.Load<Texture2D>("chars/atlika/pleased");
      scared = game.Content.Load<Texture2D>("chars/atlika/scared");
      shy = game.Content.Load<Texture2D>("chars/atlika/shy");
    }

    public void Talk()
    {
      var fess = Map.game.fess;

      var hi = new[] { "Привет!", "Привет, Атлика!", "Привет, Атлика, как дела?" };
      Map.game.AddActivity(new Dialog(this,
          () => { },
          () => { Map.InvokeDropMessages(); GirlSay("Уфф.", disgusted, 0); },
        new Dialog.Line(fess, hi[Misc.Rand.Next(hi.Length)]),
        new Dialog.Line(this, "Привет...", FacePic),
        new Dialog.Line(fess, "Можно к тебе присесть?"),
        new Dialog.Line(this, "Извини, я кое-кого жду.", disgusted),
        new Dialog.Line(fess, "Ладно...")
        ));
    }
  }

  class Fess : ActionCharacter
  {
    GenericMap lastMap = null;

    public Fess(FessGame game)
      : base(new WalkingCharacter(new CharacterMovementSheet(game.Content.Load<Texture2D>("chars/fess-chibi"))),
        game.Content.Load<Texture2D>("chars/fess-face"), "Фесс", 2, 5, 1, 0.1)
    {
    }

    internal override GenericMap Map
    {
      get
      {
        return base.Map;
      }
      set
      {
        if (value != null && lastMap != null)
        {
          var friends = lastMap.GetFaction(false);
          foreach (var f in friends)
            if(!f.Dead && f is Skeleton)
            {
              var s = (Skeleton)f;
              var cem = lastMap as Cemetery;
              if (cem != null && cem.obeliskActive && !s.bloody)
                continue;

              value.AddCharacter(f, Character.Position + Misc.RandVector(60, 30), false);
            }
        }

        if (value != null)
          lastMap = value;

        base.Map = value;
      }
    }
  }
}
